using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
using Sio.Mdm.Utils;

namespace Sio.Mdm.Server
{
    [Serializable]
    public class ServiceInformation : MarshalByRefObject, IDisposable
    {
        #region Declarations
        /// <summary>
        /// Possible services type
        /// </summary>
        public enum ServiceType : int
        {
            /// <summary>
            /// Service type is not known
            /// </summary>
            Unknonw,
            /// <summary>
            /// Service type is runnning from disk
            /// </summary>
            Disk,
            /// <summary>
            /// Service is in other <see cref="AppDomain"/>
            /// </summary>
            Remoting,
            /// <summary>
            /// Service is offered as web service
            /// </summary>
            WebService
        }
        #endregion

        #region Construction
        public ServiceInformation()
        {
        }        
        #endregion

        #region Finalizer
        ~ServiceInformation()
        {
            Dispose(false);
        }  
        #endregion

        #region Properties

        #region Public

        #region ImplementedInterfaces
        private List<string> _implementedInterfaces;
        public IList<string> ImplementedInterfaces
        {
            get
            {
                return _implementedInterfaces.AsReadOnly();
            }
        }
        #endregion 

        #region Name
        private string _name;
        /// <summary>
        /// Get or set the name of service
        /// </summary>
        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                _name = value;
            }
        }
        #endregion

        #region Address
        private string _address;
        /// <summary>
        /// Get or set address of service. This can be either web address or
        /// path to assembly file
        /// </summary>
        public string Address
        {
            get
            {
                return _address;
            }
            set
            {
                _address = value;                
                SetServiceType();

                if (Type == ServiceType.Disk && !Path.IsPathRooted(_address))
                {
#if MONO
                    _address = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _address.Replace('\\', '/'));
#else
					_address = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _address);
#endif
                }
            }
        }
        #endregion

        #region ImplementationType
        private string _implementationType;
        /// <summary>
        /// Get full type name that implements service
        /// </summary>
        public string ImplementationType
        {
            get
            {
                return _implementationType;
            }
        }
        #endregion

        #region Type
        private ServiceType _serviceType;
        /// <summary>
        /// Get type of service.
        /// </summary>
        /// <seealso cref="ServiceType"/>
        public ServiceType Type
        {
            get
            {
                return _serviceType;
            }
        }
        #endregion
        
        #region IsFromDisk
        /// <summary>
        /// Test wheater service is loaded from disk
        /// </summary>
        /// <seealso cref="Type"/>
        public bool IsFromDisk
        {
            get
            {
                return ServiceType.Disk == Type;
            }
        }
        #endregion

        #region IsRemoting
        /// <summary>
        /// Test wheater service is provided as remoting object
        /// </summary>
        /// <seealso cref="Type"/>
        public bool IsRemoting
        {
            get
            {
                return ServiceType.Remoting == Type;
            }
        }
        #endregion

        #region IsWebService
        /// <summary>
        /// Test wheater service is provided as web service
        /// </summary>
        /// <seealso cref="Type"/>
        public bool IsWebService
        {
            get
            {
                return ServiceType.WebService == Type;
            }
        }
        #endregion
        
        #endregion

        #region Internal

        #region ShortImplementationType
        private string _shortImplementationType;
        internal string ShortImplementationType
        {
            get
            {
                if (null == _shortImplementationType)
                {
                    _shortImplementationType = _implementationType.Split(',')[0];
                }

                return _shortImplementationType;
            }
        }
        #endregion

        #region DefaultInterface
        private string _defaultInterface;
        internal string DefaultInterface
        {
            get
            {
                if (null == _defaultInterface)
                {
                    _defaultInterface = ImplementedInterfaces[0];
                }

                return _defaultInterface;
            }
            set
            {
                _defaultInterface = value;
            }
        }
        #endregion

        #endregion

        #endregion

        #region Methods

        #region Public

        #region FromXML
        /// <summary>
        /// Create <see cref="Service"/> instance from passed XML configuration
        /// </summary>
        /// <param name="serviceConfig">XMl string that should be used for service configuration</param>
        /// <returns>Instance of <see cref="Service"/> class</returns>
        /// <exception cref="ArgumentException">
        /// Passes configuration XML for service is invalid. See inner exception for details.
        /// </exception>
        public static ServiceInformation FromXML(string serviceConfig)
        {
            try
            {
                List<Type> implementedInterfaces = new List<Type>();
                ServiceInformation service = new ServiceInformation();
                XmlDocument document = new XmlDocument();

                document.LoadXml(serviceConfig);

                XmlNode configNode = document.FirstChild;

                // Name of the service
                service._name = ConfigHelper.GetAttributeValueByName(configNode, "name");

                // First setup address since it's mandatory field
                XmlNode addressNode = ConfigHelper.GetNodeByName(configNode, "location");

                if (addressNode == null)
                {
                    throw new ArgumentException("Service configuration missing address. Service address or disk path must be defined!");
                }

                service.Address = ConfigHelper.GetAttributeValueByName(addressNode, "address");

                // Now implementation details
                XmlNode implmentsNode = ConfigHelper.GetNodeByName(configNode, "implements");

                if (implmentsNode != null)
                {
                    service._implementedInterfaces = new List<string>();

                    foreach (XmlNode node in implmentsNode.ChildNodes)
                    {
                        if (node is XmlComment)
                        {
                            continue;
                        }

                        // There are two possibilities ...
                        if (String.Compare(node.Name, "type", true) == 0)
                        {
                            string typeName = ConfigHelper.GetAttributeValueByName(node, "name");
                            
                            // ... either implementation type ...
                            service._implementationType = typeName;
                        }
                        else if (String.Compare(node.Name, "interface", true) == 0)
                        {
                            // ... or implemented interface
                            string interfaceName = ConfigHelper.GetAttributeValueByName(node, "name");

                            if (!String.IsNullOrEmpty(interfaceName))
                            {
                                if (!Config.IsInterfaceIgnored(interfaceName))
                                {
                                    service._implementedInterfaces.Add(interfaceName);
                                }
                            }
                        }
                    }
                }

                return service;
            }
            catch(Exception ex)
            {
                throw new ArgumentException("Service XML config is invalid", ex);
            }
        }
        #endregion

        #region FromType
        /// <summary>
        /// Create service inforamtion instance from passed type.
        /// </summary>
        /// <param name="type">Type to use for service configuration</param>
        /// <returns>Instance of <see cref="ServiceInformation"/> class</returns>
        public static ServiceInformation FromType(Type type)
        {
            try
            {
                ServiceInformation service = new ServiceInformation();

                service.Name = type.Name;
                
                if ( String.Compare
                    (Path.GetDirectoryName(type.Assembly.Location), 
                     Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory), 
                     true) == 0 )
                {
                    service._address = Path.GetFileName(type.Assembly.Location);
                }
                else
                {
                    service._address = type.Assembly.Location;
                }

                service._implementationType = String.Format("{0}, {1}", type.FullName, type.Assembly.GetName().Name);

                Type[] interfaces = type.GetInterfaces();

                if (interfaces.Length > 0)
                {
                    service._implementedInterfaces = new List<string>();

                    foreach (Type interfaceType in interfaces)
                    {
                        if (!Config.IsInterfaceIgnored(interfaceType.FullName) &&
                            !interfaceType.FullName.Contains("`"))
                        {
                            string interfaceName =
                                String.Format("{0}, {1}", interfaceType.FullName, interfaceType.Assembly.GetName().Name);

                            if (!service._implementedInterfaces.Contains(interfaceName))
                            {
                                service._implementedInterfaces.Add(interfaceName);
                            }
                        }
                    }
                }

                return service;
            }
            catch (Exception ex)
            {
                throw new Exception("Unable to create service from type. See inner excpetion for details!", ex);
            }
        }
        #endregion

        #region ToXML
        /// <summary>
        /// Return XML representation of service based on information. Suitable for
        /// configuration.
        /// </summary>
        /// <returns>XML as it would appear in configuration file for service.</returns>
        public string ToXML()
        {
            StringBuilder builder = new StringBuilder();

            builder.AppendFormat("<service name=\"{0}\">\n", Name);
            builder.AppendFormat("\t<location address=\"{0}\" />\n", Address);
            
            builder.Append("\t<implements>\n");
            builder.AppendFormat("\t\t<type name=\"{0}\" />\n", ImplementationType);
            builder.AppendFormat("\t\t<interface name=\"{0}\" />\n", DefaultInterface);

            foreach (string typeName in ImplementedInterfaces)
            {
                if (typeName == DefaultInterface || Config.IsInterfaceIgnored(typeName))
                {
                    continue;
                }

                builder.AppendFormat("\t\t<interface name=\"{0}\" />\n", typeName);
            }

            builder.Append("\t</implements>\n");
            builder.Append("</service>");

            return builder.ToString();
        }
        #endregion

        #region Implements
        /// <summary>
        /// Test wheater service implements passed interface type
        /// </summary>
        /// <param name="interfaceType">Type of interface to test for</param>
        /// <returns>True if service is implementing tested type</returns>
        public bool Implements(Type interfaceType)
        {
            foreach (string type in ImplementedInterfaces)
            {
                Type implementedInterface = System.Type.GetType(type);

                // TODO: Retest
                if ( interfaceType == implementedInterface )
                {
                    return true;
                }
            }

            return false;
        }
        #endregion

        #region Dispose
        /// <summary>
        /// To dispose resources used in instance
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
        }  
        #endregion

        #endregion

        #region SetServiceType
        private void SetServiceType()
        {
            Uri uri = GetUriFromAddress();
            
            if (uri != null)
            {
                if (String.Compare(uri.Scheme, "file", true) == 0)
                {
                    _serviceType = ServiceType.Disk;
                }
                else if (String.Compare(uri.Scheme, "tcp", true) == 0)
                {
                    _serviceType = ServiceType.Remoting;
                }
                else if (String.Compare(uri.Scheme, "http", true) == 0 ||
                         String.Compare(uri.Scheme, "https", true) == 0 ||
                         String.Compare(uri.Scheme, "ftp", true) == 0)
                {
                    _serviceType = ServiceType.WebService;
                }
                else
                {
                    _serviceType = ServiceType.Unknonw;
                }
            }
            else
            {
                _serviceType = ServiceType.Disk;
            }

            // check on disk only. The rest sould be "lazy" discovery. We don't wwant to 
            // ping all web services and remote addresses all over the place and wait timeout 
            // just to discover they are not there
            if (_serviceType == ServiceType.Disk)
            {
                if (!File.Exists(Address))
                {
                    Logger.WriteWarning("Service type is set to 'ServiceType.Disk' but file does not exist at specified address ({0})", Address);
                }
            }
        }
        #endregion

        #region GetUriFromAddress
        private Uri GetUriFromAddress()
        {
            Uri result = null;
            
            if (Uri.TryCreate(Address, UriKind.Absolute, out result))
            {
                return result;
            }

            return null;
        }
        #endregion

        #region Protected

        #region Dispose
        /// <summary>
        /// Called when used resources should be disposed
        /// </summary>
        /// <param name="disposing">True if managed resources should be disposed otherview false</param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                GC.SuppressFinalize(this);
            }
        } 
        #endregion

        #endregion

        #endregion
    }
}
